/*
* Copyright (c) 2012, Justinfront Ltd
*   author:  Justin L Mills
*   email:   JLM at Justinfront dot net
*   created: 17 June 2012
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the Justinfront Ltd nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Justinfront Ltd ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Justinfront Ltd BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


package jigsawx.examples;



import nme.Lib;
import nme.display.Sprite ;
import nme.display.Graphics;
import nme.events.MouseEvent;
import nme.events.Event;
import nme.display.Loader;
import nme.display.DisplayObject;
import nme.display.Bitmap;
import nme.display.BitmapData;
import nme.geom.Rectangle;
import nme.geom.Point;
import nme.display.PixelSnapping;
import nme.geom.Matrix;
import neash.display.BitmapInt32;
import haxe.Timer;
import nme.net.URLRequest;
import jigsawx.JigsawPiece ;
import jigsawx.Jigsawx;
import jigsawx.math.Vec2;


// can be easily modified to work with swf but currently setup for nme c++ and neko only.
class JigsawxSimple extends Sprite
{
    
    private var holder :                        Sprite;
    private var hit:                            Sprite;
    private var jigsawx :                       Jigsawx;
    private var videoSource:                    Sprite;
    private var wid:                            Float;
    private var hi:                             Float;
    private var rows:                           Int;
    private var cols:                           Int;
    private var count:                          Int;
    private var atimer:                         Timer;
    private var depth:                          Int;
    
    private var tiles:                          Array<Sprite>;
    private var surfaces:                       Array<Bitmap>;
    private var offset:                         Array<Vec2>;
    private var current:                        Sprite;
    private var spCloth:                        Sprite;
    private var loader:                         Loader;
    
    private inline static var imageSrc:        String = "tablecloth.jpg";
    
    static function main()
    {
        
        Lib.create( function()
                    { 
                        new JigsawxSimple(); 
                    },  900,500,30,0x000000
                    ,   ( 1*Lib.HARDWARE ) | Lib.RESIZABLE 
                    );
        
    } 
    
    public function new()
    {
        super();
        current                         = Lib.current;
        holder                          = new Sprite();
        holder.x                        = 0;
        holder.y                        = 0;
        
        current.addChild( holder ) ;
        count                           = 0;
        rows                            = 7;
        cols                            = 10;
        wid                             = 45;
        hi                              = 45;
        
        createVisuals();
        tableClothDisplay();
        Lib.current.addEventListener( MouseEvent.MOUSE_UP, allTilesStop );
        
    }
    
    
    public function allTilesStop( e: MouseEvent )
    {
        
        // TODO: Need to add is close snap code for this case...
        for( all in tiles ) all.stopDrag();
        
    }
    
    
    public function tableClothDisplay()
    {
        
        spCloth     = new Sprite();
        loader      = new Loader();
        
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, copyAcross );
        loader.load( new URLRequest( imageSrc ) );
        
        #if showImageSource
            holder.addChild( spCloth );
        #end
        
    }
    
    
    private function copyAcross( e: Event )
    {
        count++;
        var bmp:    Bitmap  = cast loader.content;
        
        spCloth.addChild( new Bitmap( bmp.bitmapData ) );  
        
        //if( count > 1000 ) atimer.stop();
        
        var off:    Vec2;
        var xy              = new Vec2( 0, 0 );
        var count           = 0;
        
        for( row in 0...rows )
        {
            
            for( col in 0...cols )
            {
                
                off     = offset[ count ];
                // optimisation could try render blitting to single surface and implement dragging in the same way as javascript.
                // scale 1.2 times
                surfaces[ count ].bitmapData.draw( spCloth, new Matrix( 1.2, 0, 0, 1.2, -xy.x - off.x, -xy.y - off.y) );
                
                xy.x    += wid;
                count++;
                
            }
            
            xy.x        = 0;
            xy.y        += hi;
            
        }
        
    }
    
    
    public function createVisuals()
    {
        
        var sp:             Sprite;
        var maskSp:         Sprite;
        var tile:           Sprite;
        
        var surface:        Graphics;
        tiles                                       = [];
        surfaces                                    = [];
        offset                                      = [];
        var first:          Vec2;
        
        jigsawx                                     = new Jigsawx( wid, hi, rows, cols );
        depth                                       = 0;
        
        for( jig in jigsawx.jigs )
        {
            
            // create sprite and surface and mask
            
            sp                                      = new Sprite();
            tiles.push( sp );
            holder.addChild( sp );
            tile                                    = new Sprite();
            sp.addChild( tile ); 
            
            //sp.fill                   = '#ffffff';
            
            sp.x                                    = jig.xy.x;
            sp.y                                    = jig.xy.y;
            maskSp                                  = new Sprite();
            tile.mask                               = maskSp;
            maskSp.x                                = -wid/2;
            maskSp.y                                = -hi/2;
            surface                                 = maskSp.graphics;
            
            sp.addChild( maskSp );
            
            // local copies so that local functions can get the the current loop variables, not sure if they are all needed.
            var tempSp                              = sp;
            var wid_                                = wid/2;
            var hi_                                 = hi/2;
            var ajig                                = jig;
            
            // Select some pieces out of place.
            if( Math.random()*5 > 2 )
            {
                
                sp.x                                = 900 - Math.random()*400;
                sp.y                                = 400 - Math.random()*400;
                sp.alpha                            = 0.7;
                
                drawEdge( surface, jig, 0x0000ff );
                
                sp.addEventListener( MouseEvent.MOUSE_DOWN, function( e: MouseEvent )
                {
                    
                    tempSp.parent.addChild( tempSp );
                    tempSp.startDrag();
                    
                });
                
                
                sp.addEventListener( MouseEvent.MOUSE_UP, function( e: MouseEvent )
                {
                    
                    if( Math.abs( ajig.xy.x - tempSp.x ) < ( wid_ + hi_ )/4  &&  Math.abs( ajig.xy.y - tempSp.y ) < ( wid_ + hi_ )/4 )
                    {
                        
                        tempSp.x                    = ajig.xy.x;
                        tempSp.y                    = ajig.xy.y;
                        tempSp.alpha                = 1;
                        jig.enabled                 = false;
                        tempSp.mouseEnabled         = false;
                        tempSp.mouseChildren        = false;
                        tempSp.buttonMode           = false;
                        tempSp.useHandCursor        = false;
                        
                    }
                    
                    tempSp.stopDrag();
                    
                });
                
            }
            else
            {
                
                maskSp.alpha                        = 0;
                jig.enabled                         = false;
                tempSp.mouseEnabled                 = true;
                tempSp.mouseChildren                = true;
                tempSp.buttonMode                   = true;
                tempSp.useHandCursor                = true;
                
                drawEdge( surface, jig, 0x000000 );
                
            }
            
            // significant change required for nme
            var bounds                              = getBounds( maskSp, sp );
            tile.x                                  = bounds.x;
            tile.y                                  = bounds.y;
            
            var tileW                               = Std.int( maskSp.width );
            var tileH                               = Std.int( maskSp.height );
            // for alpha colors you can't use an Int directly in nme     BitmapData.createColor( 0xff,0xffffff )
            var bd                                  = new BitmapData( tileW, tileH, true, BitmapData.createColor( 0xff,0xffffff ) );
            var bm                                  = new Bitmap( bd, PixelSnapping.ALWAYS, true );
            
            // May not need this line... possibly change bm to not transparent.
            bd.fillRect( new Rectangle( 0, 0, tileW, tileH ), BitmapData.createColor( 0xff,0x000000 ) );
            
            tile.addChild( bm );
            surfaces.push( bm );
            offset.push( new Vec2( bounds.x, bounds.y ) );
            
        }
        
    }
    
    
    public function drawEdge( surface: Graphics, jig: JigsawPiece, c: Int )
    {
        
        surface.lineStyle( 1, c, 1 );
        surface.beginFill( c, 1 );
        var first = jig.getFirst();
        
        surface.moveTo( first.x, first.y );
        
        for( v in jig.getPoints() )  {  surface.lineTo( v.x, v.y ); }
        
        surface.endFill();
        
    }
    
    
    /*____________________________________________________________________________________________________*/
    // getBounds functionality
    // source: http://www.haxenme.org/community/forums/general-discussion/getbounds-cpp-implementation/
    // author: crayfellow
    /*____________________________________________________________________________________________________*/
    
    
    public static inline function getBounds(    context:            DisplayObject
                                            ,   referenceContext:   DisplayObject
                                            ):  Rectangle 
    {
        
        var rect;
        // getBounds not yet implemented for cpp targets. here's one that will be contributed.
        // get unrotated bounds.
        var r               = context.rotation;
        context.rotation    = 0;
        var w               = context.width;
        var h               = context.height;
        context.rotation    = r;
        
        // transform context origin to reference space.
        var p               = transformBetweenContexts( context, referenceContext );
        
        // initialize rectangle at current position and unrotated dimensions.
        rect                = new Rectangle( p.x, p.y, w, h );
        
        // account for scale.
        var scale           = getAccumulatedScale( context.parent );
        rect.width          *= scale.x;
        rect.height         *= scale.y;
        
        // compute AABB for rotation.
        getRotatedBounds( rect.clone(), getAccumulatedRotation( context ), rect );
        
        return rect;
        
    }
    
    
    /**
     * Accumulate simple additive rotation.
    */
    public static inline function getAccumulatedRotation(   context:                DisplayObject
                                                        ,   ?referenceContext:      DisplayObject   = null
                                                        ,   ?depth:                 Int             = 256
                                                        ):  Float 
    {
        
        var accumulatedRotation: Float  = context.rotation;
        var l                           = 0;
        
        while( (( context = context.parent ) != null ) || l > depth )
        {
            accumulatedRotation         += context.rotation;
            if (referenceContext        != null && context == referenceContext) break;
            l++;
        }
        
        return accumulatedRotation;
        
    }
    
    
    /**
     * Scale can be accumulated through multiplication.
     */
    public static inline function getAccumulatedScale   (   context:                DisplayObject
                                                        ,   ?referenceContext:      DisplayObject   = null
                                                        ,   ?depth:                 Int             = 256 
                                                        ):  Point 
    {
        
        var accumulatedScale:Point      = new Point(context.scaleX, context.scaleY);
        var l                           = 0;
        
        while( (( context = context.parent ) != null ) || l > depth )
        {
            accumulatedScale.x          *= context.scaleX;
            accumulatedScale.y          *= context.scaleY;
            
            if( referenceContext != null && context == referenceContext ) break;
            
            l++;
            
            
        }
        
        return accumulatedScale;
        
    }
    
    
    public static inline function transformBetweenContexts(     context:            DisplayObject
                                                            ,   referenceContext:   DisplayObject
                                                            ,   ?p:                 Point           = null
                                                            ):  Point 
    {
        
        if ( p == null ) p = new Point( 0, 0 );
        return referenceContext.globalToLocal( context.localToGlobal( p ) );
        
    }
    
    
    /**
    * Precalculated coefficient used to convert degress to radians.
    */
         
    public static inline var DEGREES_TO_RADIANS: Float = Math.PI / 180.0;
    
    
    public static inline function getRotatedBounds  (           rect:               Rectangle
                                                    ,           angle:              Float
                                                    ,           fracOffsetX:        Float           = 0.5
                                                    ,           fracOffsetY:        Float           = 0.5
                                                    ,           ?targetRect:        Rectangle       = null
                                                    ): Rectangle
    {
        
        if( targetRect == null ) targetRect = new Rectangle();
        
        var width:          Float           = rect.width;
        var height:         Float           = rect.height;
        
        angle                               = modulo( angle, 360 );
        var rad                             = angle * DEGREES_TO_RADIANS;
        var absSinA:        Float           = Math.abs( Math.sin( rad ) );
        var absCosA:        Float           = Math.abs( Math.cos( rad ) );
        var widthSin                        = width * absSinA;
        var widthCos                        = width * absCosA;
        var heightSin                       = height * absSinA;
        var heightCos                       = height * absCosA;
        
        targetRect.left                     = rect.x - fracOffsetX * ( widthCos + heightSin );
        targetRect.right                    = rect.x + ( 1 - fracOffsetX ) * ( widthCos + heightSin );
        targetRect.top                      = rect.y - fracOffsetY * ( widthSin + heightCos );
        targetRect.bottom                   = rect.y + ( 1 - fracOffsetY ) * ( widthSin + heightCos );
        
        var targetWidth                     = targetRect.width;
        var targetHeight                    = targetRect.height;
        var offsetX:        Float           = 0.0;
        var offsetY:        Float           = 0.0;

        if (angle < 90) {
                                    offsetX -= heightSin;
        }
        else if( angle == 90 )
        {
                                    offsetX -= targetWidth;
        }
        else if( angle < 180 )
        {
                                    offsetX -= targetWidth;
                                    offsetY -= heightCos;
        }
        else if( angle == 180 )
        {
                                    offsetX -= targetWidth;
                                    offsetY -= targetHeight;
        }
        else if( angle < 270 )
        {
                                    offsetX -= widthCos;
                                    offsetY -= targetHeight;
        }
        else if( angle == 270 )
        {
                                    offsetY -= targetHeight;
        }
        else
        {
                                    offsetY -= widthSin;
        }
        
        targetRect.x                        += offsetX + fracOffsetX * targetWidth;
        targetRect.y                        += offsetY + fracOffsetY * targetHeight;
        
        return targetRect;
        
    }
    
    
    /**
     * Donald Knuth mod algorithm
     */
    public static inline function modulo( value: Float, divisor: Float ): Float
    {
        
        var q = Math.floor(value / divisor);
        
        return value - q * divisor;
        
    }
    
    /*____________________________________________________________________________________________________*/
    
}